/*
	GadgetBeamWeapon.cpp
	(c)1999 Palestar Development, Richard Lyle
*/


#include "Debug/Assert.h"
#include "Display/PrimitiveSetTransform.h"
#include "Display/PrimitiveTriangleFan.h"
#include "System/Messages.h"
#include "World/NounTarget.h"
#include "Constants.h"
#include "SceneryEffect.h"
#include "VerbDamageNoun.h"
#include "NounNebula.h"
#include "NounField.h"
#include "NounProjectile.h"
#include "GadgetBeamWeapon.h"
#include "GameContext.h"
#include "StructureDefense.h"

//----------------------------------------------------------------------------

IMPLEMENT_ABSTRACT_FACTORY( GadgetBeamWeapon, NounGadget ); 
REGISTER_FACTORY_KEY( GadgetBeamWeapon, 4099427496223188790 );

BEGIN_ABSTRACT_PROPERTY_LIST( GadgetBeamWeapon, NounGadget );
	ADD_TRANSMIT_UPDATE_PROPERTY( m_Energy );
	ADD_TRANSMIT_UPDATE_PROPERTY( m_Duration );
	ADD_TRANSMIT_UPDATE_PROPERTY( m_PointDefense );
	ADD_PROPERTY( m_Target );
END_PROPERTY_LIST();

GadgetBeamWeapon::GadgetBeamWeapon() 
	: m_Energy( 0 ), m_Duration( -1 ), m_PointDefense( false ), m_Signature( 0 ), m_nEnergyTick( 0 )
{}

//----------------------------------------------------------------------------

bool GadgetBeamWeapon::read( InStream & input )
{
	if (! NounGadget::read( input ) )
		return false;
	if (! m_Target.valid() && m_Duration > 0 )
		m_Duration = 0;
	return true;
}

//----------------------------------------------------------------------------

void GadgetBeamWeapon::render( RenderContext &context, 
					const Matrix33 & frame, 
					const Vector3 & position )
{
	NounGadget::render( context, frame, position );

	// render the beam
	if ( m_Duration > 0 && m_Target.valid() )
	{
		if (! context.sphereVisible( position, length() ) )
			return;

		int tailAlpha = 32;

		Noun * pTarget = m_Target;
		if ( m_Hit.valid() )
		{
			pTarget = m_Hit;
			tailAlpha = 128;
		}

		Vector3 direction( pTarget->worldPosition() - worldPosition() );
		//direction.normalize();
		//direction *= length();

		Vector3 head( position );
		Vector3 tail( head + (context.frame() * direction) );

		// calculate the material wrap
		float h = 0.05f;
		float w = 10.0f;

		Material * pTracerMaterial = tracerMaterial();
		if ( pTracerMaterial != NULL )
		{
			h = pTracerMaterial->height();
			w = pTracerMaterial->width();
			Material::push( context, pTracerMaterial );
		}
		else
			Material::push( context, Color(255,0,0), true, PrimitiveMaterial::ADDITIVE );

		float u = (head - tail).magnitude() / w;

		const Vector3	N( 0,0, 0);
		const Vector3	Y( 0, h, 0 );
		const Vector3	X( h, 0, 0 );
		const Color		HC( 255,255,255,255 );
		const Color		TC( 255,255,255,tailAlpha );

		VertexL beamY[4] = 
		{
			VertexL( head + Y, N, HC, u, 0.0f ),
			VertexL( tail + Y, N, TC, 0.0f, 0.0f ),
			VertexL( tail - Y, N, TC, 0.0f, 1.0f ),
			VertexL( head - Y, N, HC, u, 1.0f ),
		};
		VertexL beamX[4] = 
		{
			VertexL( head + X, N, HC, u, 0.0f ),
			VertexL( tail + X, N, TC, 0.0f, 0.0f ),
			VertexL( tail - X, N, TC, 0.0f, 1.0f ),
			VertexL( head - X, N, HC, u, 1.0f ),
		};
		
		DisplayDevice * pDisplay = context.device();
		ASSERT( pDisplay );

		PrimitiveSetTransform::push( pDisplay, Matrix33( true ), Vector3( 0.0f ) );
		PrimitiveTriangleFanDL::push( pDisplay, 4, beamY );
		PrimitiveTriangleFanDL::push( pDisplay, 4, beamX );
	}
}

//----------------------------------------------------------------------------

void GadgetBeamWeapon::setup()
{
	NounGadget::setup();

	// start out with full energy
	m_Energy = energyCost();
	m_Duration = -1;
	m_Signature = 0;
}

void GadgetBeamWeapon::release()
{
	NounGadget::release();

	m_Hit = NULL;
	m_Target = NULL;
	m_Duration = -1;
}

void GadgetBeamWeapon::simulate( dword nTick )
{
	if ( m_Duration > 0 )
	{
		// reduce the beam duration
		m_Duration--;

		// validate the target
		bool targetValid = false;
		if ( WidgetCast<NounTarget>( m_Target.pointer() ) != NULL )
			targetValid = true;
		else if ( m_Target.valid() && m_Target->zone() != NULL )
			targetValid = true;

		if ( targetValid )
		{
			Vector3 beamOrigin( worldPosition() );
			Vector3 beamEnd( m_Target->worldPosition() );
			Vector3 direction( beamEnd - beamOrigin );
			direction.normalize();
			direction *= length();

			// check for collision
			Array< GameContext::NounCollision > check;
			if ( context()->proximityCheck( beamOrigin, length(), check ) )
			{
				NounGame *		pCollide = NULL;
				float			maxDistance = length();
				float			collideDistance = maxDistance;
				Vector3			collidePosition = beamEnd;

				for(int i=0;i<check.size();i++)
				{
					NounGame * pCheck = WidgetCast<NounGame>( check[ i ].pNoun );
					if ( pCheck == NULL )
						continue;		
					if ( pCheck == (Noun *)parent() )
						continue;		// don't collide with our own ship!

					if ( WidgetCast<NounProjectile>( pCheck ) )
						if ( pCheck != m_Target )
							continue;		// don't destroy projectiles that are not our target!
					if ( WidgetCast<NounNebula>( pCheck ) || WidgetCast<NounField>( pCheck ) )
						continue;			// don't collide with nebula or fields

					// object is close, final check
					Vector3 intersect;
					if ( pCheck->intersect( beamOrigin, direction, intersect ) )
					{
						float distance = (beamOrigin - intersect).magnitude();
						if ( distance < collideDistance )
						{
							pCollide = pCheck;
							collideDistance = distance;
							collidePosition = intersect;
						}
					}
				}

				// does the projectile have a valid collision
				if ( pCollide != NULL )
				{
					// create the hit effect
					if ( context()->client() )
					{
						if (! m_Hit.valid() )
						{
							// create the hit effect
							SceneryEffect * pHit = new SceneryEffect;
							pHit->setNounContext( new NounContext( hitEffect() ) );
							pHit->setContext( context() );
							pHit->setPosition( collidePosition );
							pHit->setFrame( Matrix33( direction ) );

							if (! pHit->setSegment( "Effect" ) )
								pHit->setLife( 5.0f );	// default to 5 seconds

							context()->attachNoun( pHit );

							// track the current hit effect
							m_Hit = pHit;
						}
						else
						{
							// update the current hit effect position
							m_Hit->setWorldPosition( collidePosition );
						}
					}

					if ( server() && pCollide->canDamage( damageType() ) )
					{
						int inflict = damage();
						if ( damageFalloff() > 0 )
						{
							// damage is less with range
							float scale = collideDistance / maxDistance;
							inflict -= scale * damageFalloff();
						}

						if ( damageRandom() > 0 )
							inflict += rand() % damageRandom();

						if ( inflict > 0 )		// make sure the falloff didn't negate the damage
						{
							gameContext()->pushDamage( pCollide, parentBody(), inflict, damageType(), 
								pCollide->worldFrame() * (collidePosition - pCollide->worldPosition()) );
						}
					}
					else if ( WidgetCast<NounProjectile>( pCollide ) )
					{
						// explode the projectile
						((NounProjectile *)pCollide)->explode();
					}
				}
				else if ( m_Hit.valid() )
				{
					// stop the hit effect
					((SceneryEffect *)m_Hit.pointer())->setLife( 0.0f );
					m_Hit = NULL;
				}
			}
		}

		// has the beam finished
		if ( m_Duration <= 0 )
		{
			// stop any hit effect
			if ( m_Hit.valid() )
			{
				((SceneryEffect *)m_Hit.pointer())->setLife( 0.0f );
				m_Hit = NULL;
			}
			m_Target = NULL;
		}
	}

	NounGadget::simulate( nTick );
}

//----------------------------------------------------------------------------

void GadgetBeamWeapon::inflictDamage( dword nWhen, Noun * pFrom, int damage, dword type, const Vector3 & direction )
{
	NounGadget::inflictDamage( nWhen, pFrom, damage, type, direction );

	//if ( type & DAMAGE_ELF )
	//{
	//	int drained = Min( m_Energy, damage );
	//	m_Energy = Clamp( m_Energy - drained, 0, energyCost());

	//	// give the energy to the draining ship
	//	if ( WidgetCast<NounShip>( pFrom ) )
	//		((NounShip *)pFrom)->setEnergy( ((NounShip *)pFrom)->energy() + drained );
	//}
}

//----------------------------------------------------------------------------

NounGadget::Type GadgetBeamWeapon::type() const
{
	return WEAPON;
}

NounGadget::EnergyClass GadgetBeamWeapon::energyClass() const
{
	return ENERGY_CLASS_WEAPONS;
}

dword GadgetBeamWeapon::hotkey() const
{
	return HK_SPACE;
}

float GadgetBeamWeapon::addSignature() const
{
	return m_Signature;
}

bool GadgetBeamWeapon::usable( Noun * pTarget, bool shift ) const
{
	if ( shift )
		return true;		// allow point defense to be toggled
	if ( WidgetCast<NounShip>( parent() ) )
		if ( ((NounShip *)parent())->testFlags( NounShip::FLAG_CLOAKED|NounShip::FLAG_IN_SAFE_ZONE ) )
			return false;			// weapons are disabled
	if ( pTarget == NULL )
		return false;
	if ( destroyed() )
		return false;
	if ( m_Energy < energyCost() )
		return false;

	Vector3 position( pTarget->worldPosition() );
	if ( ((position - worldPosition()).magnitude() - pTarget->radius()) > length() )
		return false;

	// finally check the facing of the weapon
	return( checkFacing( position ) );
}

int GadgetBeamWeapon::usableWhen() const
{
	return energyCost() > 0 ? (100 - ((m_Energy * 100) / energyCost())) : 0;
}

bool GadgetBeamWeapon::useActive() const
{
	return m_Duration > 0;
}

//----------------------------------------------------------------------------

void GadgetBeamWeapon::use( dword when, Noun * pTarget, bool shift )
{
	if ( shift )
	{
		// toggle point defense
		m_PointDefense = !m_PointDefense;
	}
	else if ( pTarget != NULL )
	{
		m_Energy = 0;
		m_Target = pTarget;
		m_Duration = duration();
		m_nEnergyTick = when + duration();
		
		// create the use effect
		if ( context()->client() )
			createUseEffect();
	}
}

int GadgetBeamWeapon::useEnergy( dword nTick, int energy )
{
	if ( m_Energy < energyCost() )
	{
		int nElapsed = nTick - m_nEnergyTick;
		m_nEnergyTick = nTick;
		if ( nElapsed < 0 )
			return energy;		// currently firing

		int chargeRate =  (damageRatioInv() * energyCharge()) * nElapsed;
		int charge = Min( Min( energyCost() - m_Energy, chargeRate ), energy );
		// reduce ship energy
		energy -= charge;
		// charge this weapon
		m_Energy += charge;
		// calculate the signature
		m_Signature = 0.1f * charge;
	}

	return energy;
}

bool GadgetBeamWeapon::updateLogic()
{
	if ( useActive() || usableWhen() > 0 )
		return true;		// early out if not ready to fire or currently in use...

	if ( WidgetCast<NounShip>( parent() ) )
	{
		NounShip * pShip = (NounShip *)parent();

		// turn on point defense if not on already...
		if ( !pointDefense() )
			setPointDefense( true );

		Noun * pTarget = pShip->commandTarget();
		if (! pTarget )
			return true;	// no current target
		if (! isEnemy( pTarget ) )
			return true;	// don't attack friendly/neutral targets
		if ( usable( pTarget, false ) )
			pShip->useGadget( this, pTarget, false );

	}
	else if ( WidgetCast<StructureDefense>( parent() ) )
	{
		StructureDefense * pStructure = (StructureDefense *)parent();
		if (! pStructure->active() )
			return true;
		NounPlanet * pPlanet = pStructure->planet();
		if (! pPlanet )
			return false;

		Noun *	pBestTarget = NULL;
		float	fBestRange = 0.0f;;

		// look for incoming enemy projectiles, pick the best target
		for(int j=0;j<pPlanet->contactCount();j++)
		{
			NounGame * pContact = WidgetCast<NounGame>( pPlanet->contact( j ) );
			if ( pContact != NULL && pContact->enablePD() && !pPlanet->isFriend( pContact ) )
			{
				float fRange = (worldPosition() - pContact->worldPosition()).magnitude();
				if ( (!pBestTarget || fRange < fBestRange) && usable( pContact, false ) 
					&& !StructureDefense::isTargeted( pPlanet, pContact) )
				{
					pBestTarget = pContact;
					fBestRange = fRange;
				}
			}
		}

		if ( pBestTarget )
		{
			// set the target now, so no other beam weapon tries to destroy the projectile
			setTarget( pBestTarget );
			// use the beam weapon on the projectile
			pStructure->useGadget( pBestTarget, this );
			return true;
		}

		for(int j=0;j<pPlanet->contactCount();j++)
		{
			NounShip * pShip = WidgetCast<NounShip>( pPlanet->contact( j ) );
			if ( pShip != NULL && pPlanet->isEnemy( pShip ) )
			{
				float fRange = (worldPosition() - pShip->worldPosition()).magnitude();
				if ( (!pBestTarget || fRange < fBestRange) && usable( pShip, false ) )
				{
					pBestTarget = pShip;
					fBestRange = fRange;
				}
			}
		}

		if ( pBestTarget != NULL )
			pStructure->useGadget( pBestTarget, this );

		return true;
	}

	return false;
}

//----------------------------------------------------------------------------
// EOF
